/***************************************************************
 *
 * (c) 2008 Markus Dittrich 
 *
 * This program is free software; you can redistribute it 
 * and/or modify it under the terms of the GNU General Public 
 * License Version 3 as published by the Free Software Foundation. 
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License Version 3 for more details.
 *
 * You should have received a copy of the GNU General Public 
 * License along with this program; if not, write to the Free 
 * Software Foundation, Inc., 59 Temple Place - Suite 330, 
 * Boston, MA 02111-1307, USA.
 *
 ****************************************************************/

/* STL includes */
#include <limits>

/* Qt includes */
#include <QRect>

/* local includes */
#include "canvas_layout_manager.h"
#include "graph_canvas.h"
#include "utilities.h"


//----------------------------------------------------------------
// constructor
//----------------------------------------------------------------
CanvasLayoutManager::CanvasLayoutManager()
  :
    zoom_(0),
    xZoomSpeed_(0.05),
    yZoomSpeed_(0.05),
    xPan_(0),
    xPanFraction_(0.05),     
    yPan_(0),
    yPanFraction_(0.05),
    dirty_(true),
    xGlobalMax_(0.0),
    xGlobalMin_(0.0),
    yGlobalMax_(0.0),
    yGlobalMin_(0.0),
    xCurrentMax_(0.0),
    xCurrentMin_(0.0),
    yCurrentMax_(0.0),
    yCurrentMin_(0.0),
    xScaling_(0.0),
    yScaling_(0.0)
{}


//----------------------------------------------------------------
// initialize class
//----------------------------------------------------------------
void CanvasLayoutManager::initialize(const GraphCanvas* myParent)
{
  parent_ = myParent;
}


//----------------------------------------------------------------
// access function for xmin values
//----------------------------------------------------------------
double CanvasLayoutManager::xmin() const
{
  return xCurrentMin_;
}


//----------------------------------------------------------------
// access function for xmax values
//----------------------------------------------------------------
double CanvasLayoutManager::xmax() const
{
  return xCurrentMax_;
}


//----------------------------------------------------------------
// access function for ymin values
//----------------------------------------------------------------
double CanvasLayoutManager::ymin() const
{
  return yCurrentMin_;
}


//----------------------------------------------------------------
// access function for xmax values
//----------------------------------------------------------------
double CanvasLayoutManager::ymax() const
{
  return yCurrentMax_;
}


//----------------------------------------------------------------
// access function for x scaling value
//----------------------------------------------------------------
double CanvasLayoutManager::xscaling() const
{
  return xScaling_;
}


//----------------------------------------------------------------
// access function for y scaling value
//----------------------------------------------------------------
double CanvasLayoutManager::yscaling() const
{
  return yScaling_;
}


//----------------------------------------------------------------
// mark us as dirty to force recompute of layout during next
// update
//----------------------------------------------------------------
void CanvasLayoutManager::set_dirty()
{
  dirty_ = true;
}


//----------------------------------------------------------------
// update the minmax values
// NOTE: For now we start over at zoomlevel 0; there may be 
// a better way to handle these cases in the future
//----------------------------------------------------------------
void CanvasLayoutManager::update()
{
  if ( dirty_ )
  {
    compute_global_x_minmax_();
    compute_global_y_minmax_();   
    compute_x_scaling_();
    compute_y_scaling_();
 
    zoom_ = 0;

    dirty_ = false;
  }
}


/*****************************************************************
 *
 * public slots
 *
 *****************************************************************/

//----------------------------------------------------------------
// handle zoom events from the canvas
//----------------------------------------------------------------
void CanvasLayoutManager::zoom(int amount)
{
  if ( amount > 0 )
  {
    zoom_ = 1;
  }
  else if ( amount < 0 )
  {
    zoom_ = -1;
  }
  else  // no zooming, nothing more to do
  {
    return;
  }

  /* recompute current minmax and scaling */
  update_current_minmax_();
  compute_x_scaling_();
  compute_y_scaling_();
}


//----------------------------------------------------------------
// handles pan events in the x direction coming from the canvas
//----------------------------------------------------------------
void CanvasLayoutManager::pan_x(int amount)
{
  if ( amount > 0 )
  {
    xPan_ = 1;
  }
  else if ( amount < 0 )
  {
    xPan_ = -1;
  }
  else
  {
    return; 
  }

  /* recompute current minmax and scaling */
  update_current_minmax_();
  compute_x_scaling_();
}


//----------------------------------------------------------------
// handles pan events in the y direction coming from the canvas
//----------------------------------------------------------------
void CanvasLayoutManager::pan_y(int amount)
{
  if ( amount > 0 )
  {
    yPan_ = 1;
  }
  else if ( amount < 0 )
  {
    yPan_ = -1;
  }
  else
  {
    return; 
  }

  /* recompute current minmax and scaling */
  update_current_minmax_();
  compute_y_scaling_();
}


//----------------------------------------------------------------
// resets canvas to its default view, i.e., get rid of all 
// present zooming and shifting 
//----------------------------------------------------------------
void CanvasLayoutManager::reset()
{
  /* set current minmax to global minmax */
  xCurrentMin_ = xGlobalMin_;
  xCurrentMax_ = xGlobalMax_;
  yCurrentMin_ = yGlobalMin_;
  yCurrentMax_ = yGlobalMax_;

  /* recompute minmax and scaling */
  update_current_minmax_();
  compute_x_scaling_();
  compute_y_scaling_();
}


//----------------------------------------------------------------
// "zooms" into the given area on the canvas
//----------------------------------------------------------------
void CanvasLayoutManager::focus_region(double aXmin, double aXmax, 
    double aYmin, double aYmax)
{
  /* adjust viewable dimensions */
  xCurrentMin_ = aXmin;
  xCurrentMax_ = aXmax;
  yCurrentMin_ = aYmin;
  yCurrentMax_ = aYmax;

  compute_x_scaling_();
  compute_y_scaling_();
}



/*****************************************************************
 *
 * private member functions
 *
 *****************************************************************/

//---------------------------------------------------------------
// compute global x minmax based on all data sets
// 
// NOTE: If the current graphed x data is constant, i.e., 
// xGlobalMin_ == xGlobalMax_ == A, we set
// xGlobalMin_ = A - 1.0 and xGlobalMax_ = A + 1.0 to
// obtain a nicely centered display.
//---------------------------------------------------------------
void CanvasLayoutManager::compute_global_x_minmax_()
{
  /* initialize global min/max values */
  xGlobalMax_ = (-1) * std::numeric_limits<double>::max();
  xGlobalMin_ = std::numeric_limits<double>::max();

  /* we need to catch the case in which all data sets are hidden */
  bool allHidden = true;      

  FOREACH(const QString& dataName, parent_->activeData_)
  {
    /* not include hidden data sets in scaling */
    if ( is_hidden_(dataName) )
    {
      continue;
    }

    const DataObjectPtr currentData = retrieve_data_(dataName);
    double min = 0.0;
    double max = 0.0;

    if ( has_element_count_as_x(currentData) )
    {
      max = currentData->get_num_rows();
    }
    else
    {
      const std::vector<double>& xData = currentData->get_x_data();
      data_min(xData.begin(),xData.end(),min);
      data_max(xData.begin(),xData.end(),max);
    }

    /* update global minmax based on current minmax */
    if ( min < xGlobalMin_ )
    {
      xGlobalMin_ = min;
    }

    if ( xGlobalMax_ < max )
    {
      xGlobalMax_ = max;
    }

    allHidden = false;
  }

  /* adjust for constant data case */
  if ( fabs(xGlobalMax_ - xGlobalMin_) < EPSILON_DOUBLE )
  {
    xGlobalMax_ += 1.0;
    xGlobalMin_ -= 1.0;
  }

  /* adjust in case all data is hidden */
  if ( allHidden )
  {
    xGlobalMax_ = 1.0;
    xGlobalMin_ = -1.0;
  }

  /* set current minmax to global minmax */
  xCurrentMax_ = xGlobalMax_;
  xCurrentMin_ = xGlobalMin_;

  assert ( xGlobalMin_ <= xGlobalMax_ );
}


//---------------------------------------------------------------
// compute global y minmax based on all data sets
//
// NOTE: If the current graphed y data is constant, i.e., 
// yGlobalMin_ == yGlobalMax_ == A, we set
// yGlobalMin_ = A - 1.0 and yGlobalMax_ = A + 1.0 to
// obtain a nicely centered display.
//---------------------------------------------------------------
void CanvasLayoutManager::compute_global_y_minmax_()
{
  /* initialize the global min/max */
  yGlobalMax_ = (-1) * std::numeric_limits<double>::max();
  yGlobalMin_ = std::numeric_limits<double>::max();

  /* we need to catch the case in which all data sets are hidden */
  bool allHidden = true;      

  FOREACH(const QString& dataName, parent_->activeData_)
  {
    /* not include hidden data sets in scaling */
    if ( is_hidden_(dataName) )
    {
      continue;
    }

    const DataObjectPtr currentData = retrieve_data_(dataName);
    double min = 0.0;
    double max = 0.0;

    if ( has_element_count_as_y(currentData) )
    {
      max = currentData->get_num_rows();
    }
    else
    {
      const std::vector<double>& yData = currentData->get_y_data();
      data_min(yData.begin(), yData.end(), min);
      data_max(yData.begin(), yData.end(), max);
    }

    /* update global minmax based on current minmax */
    if ( min < yGlobalMin_ )
    {
      yGlobalMin_ = min;
    }

    if ( yGlobalMax_ < max )
    {
      yGlobalMax_ = max;
    }

    allHidden = false;
  }

  /* adjust for constant data case */
  if ( fabs(yGlobalMax_ - yGlobalMin_) < EPSILON_DOUBLE )
  {
    yGlobalMax_ += 1.0;
    yGlobalMin_ -= 1.0;
  }

  /* adjust in case all data is hidden */
  if ( allHidden )
  {
    yGlobalMax_ = 1.0;
    yGlobalMin_ = -1.0;
  }

  /* set current minmax to global minmax */
  yCurrentMax_ = yGlobalMax_;
  yCurrentMin_ = yGlobalMin_;

  assert ( yGlobalMin_ <= yGlobalMax_ );
}


//---------------------------------------------------------------
// updates the current minmax values based on zoomlevel
//---------------------------------------------------------------
void CanvasLayoutManager::update_current_minmax_()
{
  /* check if we did zoom and convert change into coordinates if so */
  double xScale = 0.0;
  double yScale = 0.0;
  if ( zoom_ != 0 )
  {
    xScale = xZoomSpeed_ * zoom_ * (xCurrentMax_ - xCurrentMin_);
    yScale = yZoomSpeed_ * zoom_ * (yCurrentMax_ - yCurrentMin_);
    zoom_ = 0;
  }

  /* make sure zooming stops before (max-min) hits EPSILON_DOUBLE */
  double futureDeltaX = xCurrentMax_ - xCurrentMin_ - 2.0 * xScale;
  double futureDeltaY = yCurrentMax_ - yCurrentMin_ - 2.0 * yScale;
  if ( (futureDeltaX < EPSILON_DOUBLE ) 
    || (futureDeltaY < EPSILON_DOUBLE ) )
  {
    return;
  }

  /* check if we did pan and convert change into coordinates if so */
  double xPanValue = 0.0;
  if ( xPan_ != 0 )
  {
    xPanValue = xPan_ * xPanFraction_ * (xCurrentMax_ - xCurrentMin_);
    xPan_ = 0;
  }

  double yPanValue = 0.0;
  if ( yPan_ != 0 )
  {
    yPanValue = yPan_ * yPanFraction_ * (yCurrentMax_ - yCurrentMin_);
    yPan_ = 0;
  }

  /* update minmax values */
  xCurrentMax_ = xCurrentMax_ - xScale + xPanValue;
  xCurrentMin_ = xCurrentMin_ + xScale + xPanValue;
  yCurrentMax_ = yCurrentMax_ - yScale - yPanValue;
  yCurrentMin_ = yCurrentMin_ + yScale - yPanValue;
}


//---------------------------------------------------------------
// compute proper x scaling
//---------------------------------------------------------------
void CanvasLayoutManager::compute_x_scaling_()
{
  /* the axis width/height determine the dimensions of
   * the drawable area */
  xScaling_ = parent_->axesWidth_/( xCurrentMax_ - xCurrentMin_ );

  assert ( 0 <= xScaling_ );
}


//---------------------------------------------------------------
// compute proper y scaling
//---------------------------------------------------------------
void CanvasLayoutManager::compute_y_scaling_()
{
  /* the axis width/height determine the dimensions of
   * the drawable area */
  yScaling_ = parent_->axesHeight_/ ( yCurrentMax_ - yCurrentMin_ );

  assert ( 0 <= yScaling_ );
}



//----------------------------------------------------------------
// check if data set is hidden;
// NOTE: we simply forward to our parent
//----------------------------------------------------------------
bool CanvasLayoutManager::is_hidden_(const QString& dataName) const
{
  return parent_->is_hidden_(dataName);
}


//-----------------------------------------------------------------
// retrieve data for a given data set name
// NOTE: we simply forward to our parent
//-----------------------------------------------------------------
const DataObjectPtr CanvasLayoutManager::retrieve_data_(
    const QString& dataName) const
{
  return parent_->retrieve_data_(dataName);
}



